package server;

import gui.ServerMainWindow;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Semaphore;

import common.Logger;

public class Server extends Thread {
	
	public Logger log = new Logger(false, true);
	
	private static final int defaultPort = 31000;
	private static final String defaultIp = "127.0.0.1";
	
	private static final int masterPort = 0;
	private static final String masterIp = "127.0.0.1";
	
	private static HashMap<ClientService, String> clients = new HashMap<ClientService, String>();
	
	public BackupServer bs = null;
	public MasterServer ms = null;
	
	private static Semaphore windowSemaphore = new Semaphore(1);
	
	private ServerMainWindow smw;
	
	private ServerSocket serverSocket;
	private static boolean running = false;
	
	public ServerSocket getServerSocket() {
		return serverSocket;
	}
	
	public synchronized void command(String cmd) {
		cmd = cmd.toLowerCase();
		
		ArrayList<String> cmds = new ArrayList<String>();
		
		{
			String[] tmp = cmd.split(" ");
			for (int i = 0; i<tmp.length; ++i) {
				if (tmp[i].length() != 0)
					cmds.add(tmp[i]);
			}
		
		}
		
		if (cmds.size() == 0) {
			log.log("Empty command!");
			return;
		}
			
		
		String c = cmds.get(0);
		
		if (c.equals("list")) {
			printClients();
		} else if (c.equals("list servers")) {
			printServers();
		} else if (c.equals("logout")) {
			close();
		} else if (c.equals("info")) {
			log.log("Server info:");
			log.log("   IP: " + serverSocket.getLocalSocketAddress().toString());
			log.log("   Port: " + serverSocket.getLocalPort());
			log.log("   Num. of clients: " + clients.size());
		} else if (c.equals("master")) {
			if (ms == null) {
				log.log("There is no Master Server");
			} else {
				log.log("Master server at ip: " + ms.getIp() + ", port: " + ms.getPort());
			}
		} else if (c.equals("backup")) {
			if (bs == null) {
				log.log("There is no Backup Server");
			} else {
				log.log("Backup server at ip: " + bs.getIp() + ", port: " + bs.getPort());
			}
		} else if (c.equals("kick")) {
			if (cmds.size() != 2) {
				log.log("Error: kick [clientname]");
				return;
			}
			
			for (ClientService cs : clients.keySet())
				if (clients.get(cs).toString().equalsIgnoreCase(cmds.get(1))) {
					cs.unregisterClient();
					log.log("Client " + cmds.get(1) + " kicked");
					return;
				}
			
			log.log("No such client!");
			
		} else {
			log.log("Unrecognized command: " + cmd);
		}
	}
	
	HashMap<ClientService, String> getClients() {
		return clients;
	}
	
	public void announceBackupServer() {
		synchronized(clients) {
			for (ClientService cs: clients.keySet()){
				cs.setBackupServer(bs);
			}
		}
	}
	
	synchronized boolean addClient(ClientService cs, String name) {
		synchronized(clients) {
			if (clients.containsValue(name)) {
				return false;
			} else {
				clients.put(cs, name);
				return true;
			}
			//printClients();
		}
	}
	
	synchronized void deleteClient(ClientService cs) {
		synchronized(clients) {
			clients.remove(cs);
			//printClients();
		}
	}
	
	private void printClients() {
		synchronized(clients) {
			log.log("");
			log.log( "List of clients:" );
			for (ClientService cs: clients.keySet()){
	
	           log.log(clients.get(cs).toString());  
			}
			log.log("");
		}
	}
	
	private void printServers() {
		//synchronized(servers) {
			log.log("");
			log.log( "List of servers:" );
			for (ClientService cs: clients.keySet()){
	
	           log.log(clients.get(cs).toString());  
			}
			log.log("");
		//}
	}
	
	public static void main(String[] args) {
		ServerSocket serverSocket;
		String ip;
		int port;
		
		if (args.length == 1 || args.length == 2) {
			ip = args[0];
		} else {
			ip = defaultIp;
		}
		
		if (args.length == 2) {
			port = Integer.parseInt(args[1]);
		} else {
			port = defaultPort;
		}
		

		try {
			serverSocket = new ServerSocket(port, 0, InetAddress.getByName(ip));
		} catch (UnknownHostException e1) {
			e1.printStackTrace();
			return;
		} catch (IOException e1) {
			e1.printStackTrace();
			return;
		}
		
		
		new Server(serverSocket);
		
		
	}
	
	public void windowInitialized(ServerMainWindow smw) {
		windowSemaphore.release();
		this.smw = smw;
	}
	
	public Server(ServerSocket serverSocket) {
		
		this.serverSocket = serverSocket;
		
		if (masterPort != 0)
			ms = new MasterServer(this, masterIp, masterPort, log);
		
		if (ms != null) {
			ms.start();
			log.log("Connecting to Master Server");
		}
		
		try {
			windowSemaphore.acquire();
			gui.ServerMainWindow.openWindow(this);
			windowSemaphore.acquire();
			windowSemaphore.release();
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
		
		log.log("Server starting on ip " + serverSocket.getInetAddress());
		running = true;
		
		RoomManager.setServer(this);
		
		this.start();
	}
	
	public void run() {
		Socket socket;
		
		while (running == true) {
			
			try {
				socket = serverSocket.accept();
			} catch (Exception e) {
				if (running == false) {
					log.log("Server.run() ends");
					smw.close();
					return;
				}
				
				e.printStackTrace();
				log.log("Couldn't accept new socket");
				return;
			}
			
			log.log("Connection from " + socket.getInetAddress());
			
			new ClientService(this, socket, log);
			
		}
	}
	
	public void close() {
		
		for (Map.Entry<ClientService, String> cs : clients.entrySet()) {
			log.log("Closing connection with " + cs.getValue());
			cs.getKey().close();
		}
		
		if (ms != null)
			ms.close();
		
		if (bs != null)
			bs.close();
		
		log.log("Closing ServerSocket");
		
		running = false;
		
		if (serverSocket == null) {
			log.log("Error: ServerSocket is null!");
			return;
		}
		
		try {
			log.log("Closing ServerSocket2");
			serverSocket.close();
		} catch (IOException e) {
			log.log("Closing ServerSocket failed - " + e);
		}
	}
	
}
